import { html, unsafeHTML } from '@elenajs/core'; import { LitroPage } from '@beatzball/litro/adapter/elena/page'; import { definePageData } from '@beatzball/litro'; import { createError } from 'h3'; import type { Post } from 'litro:content'; import { getPosts } from 'litro:content'; import { siteConfig } from '../../server/starlight.config.js'; import { extractHeadings, addHeadingIds } from '../../src/extract-headings.js'; import { applyHighlighting } from '../../src/highlight.js'; import { starlightHead } from '../../src/route-meta.js'; // Re-exporting child components prevents Rollup tree-shaking. export { StarlightPage } from '../../src/components/starlight-page.js'; export interface DocPageData { doc: Post; body: string; toc: Array<{ depth: number; text: string; slug: string }>; sidebar: typeof siteConfig.sidebar; siteTitle: string; currentSlug: string; prevDoc: { label: string; href: string } | null; nextDoc: { label: string; href: string } | null; nav: typeof siteConfig.nav; editUrl: string | null; } function computePrevNext( sidebar: typeof siteConfig.sidebar, currentSlug: string, ): { prevDoc: DocPageData['prevDoc']; nextDoc: DocPageData['nextDoc'] } { const flat = sidebar.flatMap(g => g.items); const idx = flat.findIndex(item => item.slug === currentSlug); return { prevDoc: idx > 0 ? { label: flat[idx - 1].label, href: `/docs/${flat[idx - 1].slug}` } : null, nextDoc: idx < flat.length - 1 ? { label: flat[idx + 1].label, href: `/docs/${flat[idx + 1].slug}` } : null, }; } export const pageData = definePageData(async (event) => { const slug = event.context.params?.slug ?? ''; const posts = await getPosts(); const doc = posts.find(p => p.url === `/content/docs/${slug}`); if (!doc) { throw createError({ statusCode: 404, message: `Doc not found: ${slug}` }); } const toc = extractHeadings(doc.rawBody); const body = applyHighlighting(addHeadingIds(doc.body)); const { prevDoc, nextDoc } = computePrevNext(siteConfig.sidebar, slug); const editUrl = siteConfig.editUrlBase ? `${siteConfig.editUrlBase}/content/docs/${slug}.md` : null; return { doc, body, toc, sidebar: siteConfig.sidebar, siteTitle: siteConfig.title, currentSlug: slug, prevDoc, nextDoc, nav: siteConfig.nav, editUrl, } satisfies DocPageData; }); export async function generateRoutes(): Promise { const posts = await getPosts(); return posts .filter(p => p.url.startsWith('/content/docs/')) .map(p => '/docs' + p.url.slice('/content/docs'.length)); } export const routeMeta = { head: starlightHead, title: 'Docs — {{projectName}}', }; export class DocPage extends LitroPage { static override tagName = 'page-docs-slug'; render() { const data = this.serverData as DocPageData | null; if (!data?.doc) return html`

Loading…

`; const { doc, body, sidebar, siteTitle, currentSlug, toc, nav, prevDoc, nextDoc, editUrl } = data; // Prev/next const prevHtml = prevDoc ? `← ${prevDoc.label}` : ''; const nextHtml = nextDoc ? `${nextDoc.label} →` : ''; const prevNextHtml = (prevDoc || nextDoc) ? `` : ''; const editHtml = editUrl ? `` : ''; return html`
${unsafeHTML(body)}
${unsafeHTML(prevNextHtml)} ${unsafeHTML(editHtml)}
`; } } DocPage.define(); export default DocPage;